錯誤往往會發生,但如何處理才是學問所在。
遇見程式發生預期外的事件,幾乎是每個工程師的日常。PHP 對於錯誤處理往往是混亂而難以控制的,這也時常成為新式語言如 Golang 或 Rust 攻擊的目標。
PHP 有時會丟出 Exception 或 Error (這兩個統稱為 Throwable)
new PDO
時設定了錯誤的資訊,這會丟出 PDOException
>>
或 <<
)位移負數位元,這會丟出 ArithmeticError
有時它會用 false
、空陣列或 null
表示函式不成功
fopen()
失敗時,會丟出一個 PHP Warning 並且回傳 false
explode()
如果在第三個參數 limit 使用負數,且第一個參數 delimiter 不存在時,會回傳空陣列json_decode()
如果發生錯誤,回傳 null
(在 PHP 7.3 後,可以使用 JSON_THROW_ON_ERROR
丟出 JsonException
)在 PHP 7.3 所提供的 Throwable 共有以下幾種(已根據繼承關係製成樹狀結構)
Error
是 PHP 7 後對於錯誤處理的重大變革,它讓語言層級的 Error 可以被捕獲並處理。
一般而言,開發者並不需要自己製作 Error
class,唯一需要的就是捕獲它們並且處理(例如用 Whoops 顯示一個對除錯友善的畫面)
Exception
算是很廣泛應用的程式語言設計,它們可以讓應用程式友善地處理例外狀況,並且開發者能夠依照自己的需求製作需要的 Exceptions
。
通常 Exception 分成兩種類型:RuntimeException
及 LogicException
。通常我會識別 RuntimeException
是「允許在服務時出現的例外」;LogicException
是「在開發階段就應該解決的例外」
function handleAuthService(string $service)
{
switch ($service) {
case 'facebook':
return new FacebookAuth();
case 'google':
return new GoogleAuth();
default:
// 使用者可能要求由 GitHub 等其它的服務登入,但系統尚未實作
// 因為不可能控制使用者的要求,所以這邊丟出 RuntimeException
throw new RuntimeException('This service is not supported.');
}
}
try {
handleAuthService($_GET['type']);
} catch (RuntimeException $e) {
abort(404, $e->getMessage());
}
內建錯誤包括但不限於 flase
、null
或空陣列。
if (! fopen('file', 'r') {
throw new Exception('File not found.');
}
通常不建議這麼做
try {
throw new class('Hello Exception') extends Exception {};
} catch (Exception $e) {
var_dump($e->getMessage());
}
該使用 RuntimeException
時就用,該使用 LogicException
時就用。
盡量在丟的時候不要只丟 Exception
,這樣有助於理解並處理它們。
如果在 catch
區塊另外丟出 Exception
,在 new
的時候必須附上第三個參數 $previous
try {
$dbh = new PDO($dsn, $user, $password);
} catch (PDOException $e) {
throw new DatabaseConnectException(
'Cannot connet to database.',
0,
$e // 這個參數務必記得加,這有助於在找 Exception Stack Trace 時候找到關連
);
}
儘管在 PHP 7 之後錯誤處理做了大量的改進,但仍然還處於一個混沌的狀態:太多的歷史包袱導致難以變更,PHP 社群的開發能量日益退化與 RFC 投票上的保守思維,這些都導致了 PHP 處於難以前進的阻礙。
註:其實並不是說保守思維不好,畢竟有太多需要考量的東西。目前核心開發組的成員都以不更動 API 為前提進行核心改進(例如 7.4 的 Preload 或 8.0 的 JIT),對於廢棄 API 或變更行為除非存在很大的問題,否則投票通常都不會通過。
註:PHP 核心開發能量日益退化,主因是 Zend API 非常難寫(如果有寫過 PHP Extension 的人應該能理解),當時在 PHP 5.x 的時候有一批開發者退出去做 NodeJS 及 Golang,這也直接導致了 PHP 6 被併入 5.6 的慘案,直到有人主導了 PHP 7(當時稱為 PHP-NG)的開發才穩定下來。
少一個括號:
可以用函名類別丟出 Exception
...
try {
throw new class('Hello Exception') extends Exception {};
} catch (Exception $e) {
var_dump($e->getMessage();
// ----------------------^
}